local net = {}

local gfx = require("libs/gfx")

local tl_failure, tl_would_block, tl_conn_reset, tl_disconnected = -1, -2, -3, -4

ffi.cdef [[
	typedef union { int _int; void* _voidp; } tl_socket;

	typedef struct tl_internet_addr tl_internet_addr;

	tl_socket tl_socket_invalid();

	int  tl_socket_is_valid(tl_socket sock);
	void tl_socket_close();

	tl_socket tl_tcp_socket();
	tl_socket tl_udp_socket();
	
	void      tl_set_nonblocking(tl_socket sock, int no_blocking);
	int       tl_set_nodelay(tl_socket sock, int no_delay);
	int       tl_bind(tl_socket sock, int port);
	int       tl_listen(tl_socket sock);
	tl_socket tl_accept(tl_socket sock, tl_internet_addr* addr);
	int       tl_connect(tl_socket sock, tl_internet_addr* addr);
	
	/* Communication */
	int tl_send(tl_socket sock, void const* msg, size_t len);
	int tl_recv(tl_socket sock, void* msg, size_t len);
	int tl_sendto(tl_socket sock, void const* msg, size_t len, tl_internet_addr const* dest);
	int tl_recvfrom(tl_socket sock, void* msg, size_t len, tl_internet_addr* src);
	
	int tl_opt_error(tl_socket sock);
	
	enum
	{
		TL_SS_MAXSIZE = 128,                 // Maximum size
		TL_SS_ALIGNSIZE = sizeof(int64_t), // Desired alignment
		TL_SS_PAD1SIZE = TL_SS_ALIGNSIZE - sizeof(short),
		TL_SS_PAD2SIZE = TL_SS_MAXSIZE - (sizeof(short) + TL_SS_PAD1SIZE + TL_SS_ALIGNSIZE)
	};

	typedef struct tl_sockaddr_storage
	{
		short ss_family;               // Address family.

		char _ss_pad1[TL_SS_PAD1SIZE];  // 6 byte pad, this is to make
									   //   implementation specific pad up to
									   //   alignment field that follows explicit
									   //   in the data structure
		int64_t _ss_align;            // Field to force desired structure
		char _ss_pad2[TL_SS_PAD2SIZE];  // 112 byte pad to achieve desired size;
									   //   _SS_MAXSIZE value minus size of
									   //   ss_family, __ss_pad1, and
									   //   __ss_align fields is 112
	} tl_sockaddr_storage;

	typedef struct tl_internet_addr
	{
		tl_sockaddr_storage storage_;
	} tl_internet_addr;

	void tl_internet_addr_init_empty(tl_internet_addr* self); // any
	void tl_internet_addr_init_name(tl_internet_addr* self, char const* name, int port);
	
	// NOTE: This may not get the address unless I/O has occured
	// on the socket or (if applicable) a connect or accept has
	// been done.
	void tl_internet_addr_init_from_socket(tl_internet_addr* self, tl_socket s);
	void tl_internet_addr_init(tl_internet_addr* self, uint32_t ip, int port);
	
	int tl_internet_addr_valid(tl_internet_addr* self);
	
	int  tl_internet_addr_port(tl_internet_addr const* self);
	void tl_internet_addr_set_port(tl_internet_addr* self, int port_new);
    
	uint32_t tl_internet_addr_ip(tl_internet_addr const* self);
	void     tl_internet_addr_set_ip(tl_internet_addr* self, uint32_t ip_new);
    
	void tl_internet_addr_reset(tl_internet_addr* self);

	int tl_internet_addr_eq(tl_internet_addr const*, tl_internet_addr const*);
]]

local n = gfx.g

local temp_buffer = ffi.new("uint8_t[2048]")

local function init_sockets(node, port)
	if node.sock then
		n.tl_socket_close(node.sock)
	end
	node.port = port
	node.sock = n.tl_udp_socket()
	n.tl_bind(node.sock, port)
end

local link_mt = {
	initiate = function(self)
		-- TODO: Send init packet
	end
}

local node_mt = {
	host = function(self, port)
		init_sockets(self, port)

		self.accept_connections = true
	end,

	connect = function(self, name, port)
		init_sockets(self, 0)

		local addr = n.tl_internet_addr()
		n.tl_internet_addr_init_name(addr, name, port)

		local new_link = self:add_link(addr)
		new_link:initiate()
		self.server_link = new_link

		self.accept_connections = false
	end,

	add_link = function(self, addr)
		local link = setmetatable({
			owner = self,
			id = self.next_link_id,
			addr,
			self.sock
		}, link_mt)

		self.next_link_id = self.next_link_id + 1

		self.links[#self.links + 1] = link
	end
}

node_mt.__index = node_mt

function net.create_node()
	local node = {
		links = {},
		next_link_id = 0,
		port = 0,
		accept_connections = false
	}

	setmetatable(node, node_mt)

	return node
end

local port = 31337

local host = n.tl_udp_socket()
n.tl_bind(host, port)

local host_addr = ffi.new("tl_internet_addr")
n.tl_internet_addr_init_name(host_addr, "localhost", port)

local client = n.tl_udp_socket()
n.tl_bind(client, 0)

local r = n.tl_sendto(client, "hello", 5, host_addr)
print("tl_sendto(...) = " .. r)

local buffer = ffi.new("uint8_t[2048]")
repeat
	local from_addr = ffi.new("tl_internet_addr")
	r = n.tl_recvfrom(host, buffer, 2048, from_addr)
	print(r)
until r > 0

local received = ffi.string(buffer, r)
print("Got: " .. received)

return net